﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using ProjectEuler.Extensions;
using ProjectEuler.Linq;
using System.Windows;

namespace ProjectEuler.UI
{
    internal sealed class ProblemRun : DependencyObject
    {
        private static readonly DependencyPropertyKey CurrentStatusPropertyKey;
        public static readonly DependencyProperty CurrentStatusProperty;

        private static readonly DependencyPropertyKey PercentCompletePropertyKey;
        public static readonly DependencyProperty PercentCompleteProperty;

        private static readonly DependencyPropertyKey IsExecutingOrStoppingPropertyKey;
        public static readonly DependencyProperty IsExecutingOrStoppingProperty;

        private static readonly DependencyPropertyKey LastRunResultPropertyKey;
        public static readonly DependencyProperty LastRunResultProperty;

        private readonly List<ProblemInfo> _selected = new List<ProblemInfo>();

        private volatile Boolean _stop;
        private volatile Int32 _current;

        public event EventHandler Completed = delegate { };

        static ProblemRun()
        {
            CurrentStatusPropertyKey = DependencyProperty.RegisterReadOnly("CurrentStatus", typeof(ProblemRunStatus),
                typeof(ProblemRun), new FrameworkPropertyMetadata(ProblemRunStatus.None));

            IsExecutingOrStoppingPropertyKey = DependencyProperty.RegisterReadOnly("IsExecutingOrStopping", typeof(Boolean),
                typeof(ProblemRun), new FrameworkPropertyMetadata(false));

            PercentCompletePropertyKey = DependencyProperty.RegisterReadOnly("PercentComplete", typeof(Int32),
                typeof(ProblemRun), new FrameworkPropertyMetadata(0));

            LastRunResultPropertyKey = DependencyProperty.RegisterReadOnly("LastRunResult", typeof(ProblemRunResult),
                typeof(ProblemRun), new FrameworkPropertyMetadata(null));

            CurrentStatusProperty = CurrentStatusPropertyKey.DependencyProperty;
            IsExecutingOrStoppingProperty = IsExecutingOrStoppingPropertyKey.DependencyProperty;
            PercentCompleteProperty = PercentCompletePropertyKey.DependencyProperty;
            LastRunResultProperty = LastRunResultPropertyKey.DependencyProperty;
        }

        public ProblemRunStatus CurrentStatus
        {
            get { return (ProblemRunStatus)GetValue(CurrentStatusProperty); }
            private set { SetValue(CurrentStatusPropertyKey, value); }
        }

        public Boolean IsExecutingOrStopping
        {
            get { return (Boolean)GetValue(IsExecutingOrStoppingProperty); }
            private set { SetValue(IsExecutingOrStoppingPropertyKey, value); }
        }

        public Int32 PercentComplete
        {
            get { return (Int32)GetValue(PercentCompleteProperty); }
            private set { SetValue(PercentCompletePropertyKey, value); }
        }

        public ProblemRunResult LastRunResult
        {
            get { return (ProblemRunResult)GetValue(LastRunResultProperty); }
            private set { SetValue(LastRunResultPropertyKey, value); }
        }

        public void Start(IEnumerable<ProblemInfo> problems)
        {
            _selected.Clear();
            _selected.AddRange(problems);

            _stop = false;
            _current = 0;

            if (_selected.Count > 0)
            {
                PercentComplete = 0;
                IsExecutingOrStopping = true;
                LastRunResult = ProblemRunResult.InProgress;
                CurrentStatus = ProblemRunStatus.Executing;

                ThreadPool.QueueUserWorkItem(unused => ExecuteNextProblem());
            }
        }

        public void Stop()
        {
            if (CurrentStatus == ProblemRunStatus.Executing)
            {
                LastRunResult = ProblemRunResult.Stopping;
                CurrentStatus = ProblemRunStatus.Stopping;
            }

            _stop = true;
        }

        private void UpdatePercentComplete()
        {
            PercentComplete = (Int32)((100.0 * _current) / _selected.Count);
        }

        private void ExecuteNextProblem()
        {
            if (!_stop)
            {
                _selected[_current].Execute();
                _current++;

                Dispatcher.BeginInvoke(UpdatePercentComplete);
            }

            if ((!_stop) && (_current < _selected.Count))
            {
                ThreadPool.QueueUserWorkItem(unused => ExecuteNextProblem());
            }
            else
            {
                Dispatcher.BeginInvoke(delegate
                {
                    CurrentStatus = ProblemRunStatus.None;
                    PercentComplete = 100;
                    IsExecutingOrStopping = false;

                    if (_stop)
                    {
                        LastRunResult = ProblemRunResult.Stopped;
                    }
                    else
                    {
                        LastRunResult = new ProblemRunResult(_selected);

                        if (_selected.Any(p => p.CurrentStatus != ProblemStatus.Correct))
                        {
                            _selected
                                .Where(p => p.CurrentStatus == ProblemStatus.Correct)
                                .ForEach(p => p.IsSelected = false);
                        }
                    }

                    Completed(this, EventArgs.Empty);
                });
            }
        }
    }
}
